package com.sghd.fight.module.game.service.room;

import com.sghd.common.utils.ManagedException;
import com.sghd.common.utils.math.RandomUtils;
import com.sghd.fight.module.game.config.RoomConfig;
import com.sghd.fight.module.game.config.SkillConfig;
import com.sghd.fight.module.game.core.Chess;
import com.sghd.fight.module.game.core.ChessBoard;
import com.sghd.fight.module.game.core.Grid;
import com.sghd.fight.module.game.core.model.ChessState;
import com.sghd.fight.module.game.core.model.EliminateResult;
import com.sghd.fight.module.game.core.model.Unit;
import com.sghd.fight.module.game.core.model.UnitState;
import com.sghd.fight.module.game.event.ExitEvent;
import com.sghd.fight.module.game.event.GameServerEvent;
import com.sghd.fight.module.game.event.PushEvent;
import com.sghd.fight.module.game.service.GameService;
import com.sghd.fight.module.game.service.skill.ConfigService;
import com.sghd.fight.module.game.service.skill.effect.Effect;
import com.sghd.fight.module.game.service.work.RoomRunner;
import com.sghd.fight.module.game.service.work.RoomTimer;
import com.sghd.fight.module.game.service.work.RoomWork;
import com.sghd.fight.net.SocketEnum;
import com.sghd.fight.utils.Callback;
import com.sghd.fight.utils.Random;
import com.sghd.pojo.candy.fight.EnterVo;
import com.sghd.pojo.candy.fight.dto.SettleTo;
import com.sghd.pojo.fight.game.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

/**
 * @author frank
 * @date 2018-10-22 16:18
 * @desc 房间
 **/
public abstract class IRoom {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    public static final int BEGIN = 1;
    public static final int INITROUND = 1;
    public static final int INIT = 0;
    public static final int END = -1;
    protected long id;
    /** 房间类型*/
    protected RoomType type;
    /** 定时工作器*/
    protected RoomTimer timer;
    /** 玩家*/
    protected ConcurrentMap<Long, Unit> units = new ConcurrentHashMap<>();
    /** 当前回会合的玩家*/
    protected Unit current;
    /** 棋盘*/
    protected ChessBoard chessBoard;
    /** 房间状态*/
    protected int state;
    /** 托管定时器*/
    protected RoomWork roomWork;
    protected int trusheeIndex;
    /** 托管的玩家*/
    protected ConcurrentMap<Long, Unit> trushees = new ConcurrentHashMap<>();
    // 总回合数记录
    protected int countRound = 0;
    protected ConfigService configService;
    protected GameService gameService;
    /** 是否是测试房间*/
    private boolean test;

    public IRoom(long id, RoomType type, RoomTimer timer, ConfigService configService, long seed) {
        this.id = id;
        this.type = type;
        this.timer = timer;
        this.gameService = (GameService) timer;
        this.configService = configService;
        this.chessBoard = new ChessBoard(getConfig().getRow(), getConfig().getCol(), new Random(seed));
        this.state = INIT;
    }

    public RoomType getType() {
        return type;
    }

    public void join(Unit unit) {
        units.put(unit.getId(), unit);
        if (unit.getType() == UnitEnum.PLAYER) {
            EnterVo enterVo = EnterVo.valueOf(unit.getId(), type);
            /** 通知大厅玩家进入游戏*/
            GameServerEvent event = GameServerEvent.valueOf(enterVo, SocketEnum.REQUEST_PLAYER_ENTER, null);
            post(GameServerEvent.NAME, event);
        }
    }

    public void join(Unit... units) {
        //玩家进入房间
        for (Unit unit : units) {
            join(unit);
        }
    }

    //结束托管
    public void endTrustee(Long playerId) {
        if (!units.containsKey(playerId)) {
            return;
        }
        Unit unit = units.get(playerId);
        if (!unit.hasState(UnitState.TRUSTEE)) {
            return;
        }
        unit.removeState(UnitState.TRUSTEE);
        trushees.remove(playerId);
        Map<String, Object> body = new HashMap<>();
        body.put("playerId", playerId);
        syncPost(PushEvent.NAME, PushEvent.valueOf(body, units.keySet(), SocketEnum._END_TRUSTEE));
    }

    //双方玩家是否准备完毕
    public boolean ready(long playerId) {
        boolean flag = true;
        if (units.get(playerId).getReady()) {
            return false;
        }
        units.get(playerId).setReady(true);
        for (Unit unit : units.values()) {
            if (!unit.getReady()) {
                flag = false;
            }
        }
        if (flag) {
            start();
        }
        return true;
    }

    public void start() {
        if (state == BEGIN || isOver()) {
            return;
        }
        state = BEGIN;
        for (Unit unit : units.values()) {
            unit.setReady(true);
        }
        //TODO 模拟棋盘
        //moni();
        roundStart(new Date());
        //timer(new Date());
    }

    private void moni() {
        String text = "电\t紫\t紫\t黄\t蓝\t电\t绿\t红\t\n" +
                "蓝\t黄\t紫\t电\t红\t蓝\t紫\t蓝\t\n" +
                "绿\t红\t绿\t红\t紫\t黄\t紫\t紫\t\n" +
                "红\t绿\t蓝\t紫\t蓝\t紫\t绿\t紫\t\n" +
                "蓝\t蓝\t绿\t蓝\t红\t紫\t黄\t绿\t\n" +
                "绿\t紫\t电\t红\t蓝\t绿\t黄\t绿\t\n" +
                "蓝\t红\t蓝\t蓝\t红\t黄\t红\t红\t\n" +
                "紫\t蓝\t紫\t绿\t紫\t紫\t电\t紫";
        String[] textS = text.split("\n");
        String[] chess = new String[64];

        for (int i = 0; i < textS.length; i++) {
            String[] col = textS[i].split("\t");
            for (int k = 0; k < col.length; k++) {
                chess[k * 8 + i] = col[k];
            }
        }
        for (int i = 0; i < chess.length; i++) {
            Chess chess1 = new Chess(ChessType.valueOfShow(chess[i]));
            chessBoard.getGrids()[i].setChess(chess1);
        }
        chessBoard.getRandom().setSeed(162217008136655L);
    }

    /**
     * 具体执行的交换方法
     * @param playerId 执行交换玩家的ID
     * @param first 第一颗棋子
     * @param second 第二颗棋子
     * @return 返回交换的结果
     */
    public EliminateResult move(long playerId, int first, int second) {
        EliminateResult result = null;
        result = chessBoard.swap(first, second, false);
        Unit unit = units.get(playerId);
        if (unit == null) {
            return result;
        }
        checkAndCollect(result, unit);
        logger.debug("当前第[{}]回合，随机种子 :{} ", countRound, chessBoard.getRandom().getSeed());
        return result;
    }

    /**
     * 检查是否增加回合，并收集
     * @param result
     * @param unit
     */
    public void checkAndCollect(EliminateResult result, Unit unit) {
        checkRound(result, unit);
    }


    public void checkRound(EliminateResult result, Unit unit) {
        //回合数+1
        for (Set<Set<Integer>> elimtByDown : result.getElimtByDown()) {
            for (Set<Integer> down : elimtByDown) {
                if (down.size() >= 4) {
                    logger.debug("额外获得一个回合点！");
                    unit.increase();
                    break;
                }
            }
        }
    }

    public void collect(EliminateResult result, Unit unit) {
        Player player = (Player) unit;
        //魔法值的收集
        //被诅咒
        if (unit.hasBuf(UnitValue.CURSE)) {
            result.getList().stream().map(chess -> chess.getType()).collect(Collectors.toSet()).forEach(type -> {
                player.getMagic().collect(type);

            });
        } else {
            for (int i = 0; i < result.getList().size(); i++) {
                player.getMagic().collect(result.getList().get(i).getType());
            }
        }
    }

//    public void timer(Date now) {
//        if (isOver()) {
//            return;
//        }
//        int[] limit = new int[]{3000, 5000};
//        int next = RandomUtils.betweenInt(limit[0], limit[1], false);
//        timer.addTimer(new RoomWork(this, new RoomRunner() {
//            @Override
//            public void run(Date now) {
//                isRobot(now);
//            }
//        }, new Date(now.getTime() + next)));
//    }

//    /**
//     * 托管行动
//     * @param now
//     */
//    public void isRobot(Date now) {
//        if (isOver()) {
//            return;
//        }
//        //当回合切换时  判断当前玩家是否是托管
//        if (current.hasState(UnitState.TRUSTEE)) {
//            trusteeResult(current.getId());
//        }
//        timer(now);
//    }

    //回合开始 执行定时器
    public void roundStart(Date now) {
        countRound++;
        boolean flag = false;
        Unit next = getNextRound();
        //回合数加一
        next.increase();
        //第一个回合
        if (current == null) {
            current = next;
            //如果第一回合的先手者是 机器人 就将它替换为玩家
            if (current.getType() == UnitEnum.ROBOT) {
                current = getNextRound();
                current.increase();//回合数加一
                next.decrease();
            }
            if (current.getType() == UnitEnum.PLAYER) {
                flag = true;
                if (getNextRound().getType() != UnitEnum.ROBOT) {
                    ((Player) getNextRound()).initUpMagic();
                }
            }
        } else {
            //当前玩家回合结束
            current.roundEnd();
            //下一轮玩家回合开始
            next.roundBegin();
            //回合正常切换
            if (next.getRound() > 0) {
                current = next;
                logger.debug("回合正常切换，当前回合的玩家[{}]", current.getId());
            } else {
                //next 回合被跳过
                next.roundEnd();
                //被跳过的玩，推送回合数据
                pushRoundData(now, flag, next, true);
                current.roundBegin();
                current.increase();
                logger.debug("玩家[{}]回合被跳过！", next.getId());
            }
        }
        pushRoundData(now, flag, current, false);
        logger.debug("当前第[{}]回合，玩家[{}]", countRound, current.getId());
        //下一回合计时
        if (this.getType() == RoomType.PVP) {
            roundTimer(now);
        }
    }

    public void pushRoundData(Date now, boolean flag, Unit current, boolean skip) {
        Map<Long, ValueItem> values = new HashMap<>();
        Map<Long, Map<ChessType, Integer>> magics = new HashMap<>();
        for (Unit unit : units.values()) {
            //获取HP
            values.put(unit.getId(), ValueItem.valueOf(UnitValue.HP, unit.getValue(UnitValue.HP)));
            //获取玩家的魔法值
            magics.put(unit.getId(), unit.getMagic().getMagics());
        }
        RoundData roundData = new RoundData(current.getId(), flag, now.getTime() + getConfig().getRoundTime(), values, magics, skip);
        syncPost(PushEvent.NAME, PushEvent.valueOf(roundData, units.keySet(), SocketEnum._START));
    }

    public void roundTimer(Date now) {
        if (roomWork != null) {
            logger.debug("移除当前时钟，当前时钟剩余[{}]秒！", (roomWork.getEnd().getTime() - System.currentTimeMillis()));
            logger.debug("结束时间点[{}]", roomWork.getEnd().getTime());
            timer.removeWork(roomWork);
        }
        // 托管  回合判断的计时器
        roomWork = new RoomWork(this, new RoomRunner() {
            @Override
            public void run(Date now) {
                if (isOver()) {
                    return;
                }
                if (!current.hasState(UnitState.TRUSTEE)) {
                    logger.debug("RoomWork 执行！");
                    trustee(current);
                }
            }
        }, new Date(now.getTime() + getConfig().getRoundTime() + 500));
        timer.addTimer(roomWork);
    }


    public void trustee(Unit player) {
        if (!trushees.containsKey(player.getId())) {
            trushees.put(player.getId(), player);
        }
        logger.debug("当前第[{}]回合，房间[{}]玩家[{}]进入托管状态!", countRound, id, player.getId());
        player.addState(UnitState.TRUSTEE);
        player.setNextSeed(System.currentTimeMillis());
        Map<String, Object> map = new HashMap<>();
        map.put("playerId", player.getId());
        map.put("random", player.getNextSeed());
        player.setFirst(true);
        syncPost(PushEvent.NAME, PushEvent.valueOf(map, units.keySet(), SocketEnum._START_TRUSTEE));
        if (current == player) {
            //立即进行一次消除
            ((Player) current).doAction();
            //有可能 还有回合继续执行
            ((Player) current).action();
        }
    }

//    /**
//     * 进入托管返回  然后执行交换
//     * @param playerId 玩家ID
//     * @return
//     */
//
//    public void trusteeResult(long playerId) {
//        Unit unit = units.get(playerId);
//        if (unit.isFirst()) {
//            chessBoard.getRandom().setSeed(unit.getNextSeed());
//            unit.setFirst(false);
//        }
//        if (trushees.size() == 2) {
//            if (trusheeIndex >= Integer.MAX_VALUE) {
//                logger.debug("双方玩家托管 ！");
//                gameOver(ExitReason.TRUSTEESHIP);
//                return;
//            } else {
//                trusheeIndex++;
//            }
//        } else {
//            trusheeIndex = 0;
//        }
//        List<List<Integer>> list = chessBoard.canElim(true);
//        Collections.shuffle(list);
//        //普通托管
//        logger.debug("当前第[{}]回合，玩家[{}]托管行动[{}],[{}]！", getCountRound(), id, list.get(0).get(0), list.get(0).get(1));
//        try {
//            action(id, list.get(0).get(0), list.get(0).get(1));
//        } catch (Exception e) {
//            logger.debug("托管行动发生错误[{}]", e.getMessage());
//        }
//    }

    public void dieChess(long playerId) {
        chessBoard.clear();
        chessBoard.genarete();
        syncPost(PushEvent.NAME, PushEvent.valueOf(playerId, units.keySet(), SocketEnum._RESET_CHESS));
    }

    /**
     * 获取下回合玩家
     * @return 下回合玩家
     */
    public Unit getNextRound() {
        Unit player = null;
        if (current == null) {
            player = new ArrayList<>(units.values()).get(RandomUtils.nextInt(units.size()));
        } else {
            for (Unit unit : units.values()) {
                if (current != unit) {
                    player = unit;
                    break;
                }
            }
        }
        return player;
    }

    /**
     * 交换外层逻辑
     * @param playerId 玩家ID
     * @param first 交换的第一颗棋子
     * @param second 交换的第二颗棋子
     */
    public void action(long playerId, int first, int second) {
        if (isOver()) {
            throw new ManagedException(-1, "游戏已经结束");
        }
        if (state != BEGIN) {
            throw new ManagedException(-1, "游戏还未开始");
        }
        Unit unit = units.get(playerId);
        if (current != unit) {
            throw new ManagedException(-1, "不是当前玩家");
        }
        if (unit.getRound() < INITROUND) {
            throw new ManagedException(-1, "玩家剩余回合数不足");
        }

        Date date = new Date();
        MoveResp rsp = new MoveResp(playerId, first, second, date.getTime() + configService.getRoomConfig(type).getRoundTime());
        if (this.getType() == RoomType.PVP) {
            roundTimer(date);
        }
        EliminateResult result = move(playerId, first, second);
        List<List<Integer>> list = chessBoard.canElim(true);
        if (list.isEmpty()) {
            rsp.setReset(true);
            chessBoard.clear();
            chessBoard.genarete();
        }
        logger.debug("推送棋子交换消息");
        syncPost(PushEvent.NAME, PushEvent.valueOf(rsp, units.keySet(), SocketEnum._MOVE));
        //判断是否执行闪电技能
        checkSkill(result);
        //回合数减一
        unit.decrease();
        logger.debug("当前第[{}]回合，玩家[{}]剩余回合数：{}", countRound, playerId, unit.getRound());
        endTurn();
    }

    /**
     * 寻找白色棋子，造成伤害
     * @param result
     */
    public void checkSkill(EliminateResult result) {
        if (isOver()) {
            logger.debug("游戏已经结束！");
            return;
        }
        int count = 0;
        //每次下落
        for (Set<Set<Chess>> chessTyps : result.getElimitByChess()) {
            for (Set<Chess> chessTyp : chessTyps) {
                for (Chess chess : chessTyp) {
                    if (chess.getType() != ChessType.CT_LIGHTNING) {
                        continue;
                    }
                    if (chess.hasStatus(ChessState.LIGHTNING2)) {
                        count += 2;
                    } else if (chess.hasStatus(ChessState.LIGHTNING3)) {
                        count += 3;
                    } else {
                        count += 1;
                    }
                }
            }
        }

//        for (Chess chess : result.getList()) {
//            if (chess.getType() != ChessType.CT_LIGHTNING) {
//                continue;
//            }
//            if (chess.hasStatus(ChessState.LIGHTNING2)) {
//                count += 2;
//            } else if (chess.hasStatus(ChessState.LIGHTNING3)) {
//                count += 3;
//            } else {
//                count += 1;
//            }
//        }
        if (count != 0) {
            Map<String, Object> ctx = new HashMap<>();
            ctx.put(Effect.LIGHTING, count);
            logger.debug("执行白色棋子技能效果");
            //执行技能
            SkillReport report = skilled(current, 0, ctx);
            //推送给客户端
            logger.debug("推送白色棋子技能效果");
            syncPost(PushEvent.NAME, PushEvent.valueOf(report, new ArrayList<>(units.keySet()), SocketEnum._EXECUTE_SKILL));

        }
    }

    public void exit(long playerId) {
        units.remove(playerId);
    }

    //判断是否应该结束回合
    public boolean isEndTurn() {
        boolean flag = true;
        if (current.getRound() > INIT) {
            flag = false;
        }
        return flag;
    }

    public ConcurrentMap<Long, Unit> getTrushees() {
        return trushees;
    }

    public int getTrusheeIndex() {
        return trusheeIndex;
    }

    public void setTrusheeIndex(int trusheeIndex) {
        this.trusheeIndex = trusheeIndex;
    }

    protected SkillReport skilled(Unit player, int skillId, Map<String, Object> ctx) {
        if (isOver()) {
            throw new ManagedException(-1, "游戏已经结束");
        }
//        if (current != player) {
//            throw new ManagedException(-1, "不是当前玩家的回合!！");
//        }
        SkillConfig skillConfig = configService.getSkillConfig(skillId);
        if (skillConfig == null) {
            throw new ManagedException(-1, "技能不存在！");
        }
        //消耗
        player.consume(skillConfig);

        SkillReport report = player.execute(this, skillConfig, ctx);
        //每个玩家的血量绝对值
        for (TargetReport targetReport : report.getReports()) {
            Unit tar = units.get(targetReport.getTarget());
            List<ValueItem> abs = new ArrayList<>();
            abs.add(ValueItem.valueOf(UnitValue.HP, tar.getValue(UnitValue.HP)));
            targetReport.setAbs(abs);
            if (tar instanceof Player) {
                targetReport.setMagics(new HashMap<>(((Player) tar).getMagic().getMagics()));
            }
        }
        //施放者最新的魔法值
        report.setMagics(new HashMap<>(player.getMagic().getMagics()));
        return report;
    }

    public void endTurn() {
        if (isOver()) {
            return;
        }
        timer.submit(this, now -> {
            if (isEndTurn() && !isOver()) {
                logger.debug("玩家[{}]回合结束！", current.getId());
                roundStart(new Date());
            }
        });
    }

    /**
     * @param player 死亡的玩家
     */
    public void dead() {
        //删除定时器
        timer.removeWork(roomWork);
        gameOver(ExitReason.DEAD);
        logger.debug("房间内，玩家[{}]死亡！");
        //玩家死亡
    }

    /***
     * 游戏结束
     */
    public void gameOver(ExitReason reason) {
        this.state = END;
        logger.debug("游戏结束！");
        submit(new RoomRunner() {
            @Override
            public void run(Date now) {
                Collection<Long> playerIds = new ArrayList<>(units.keySet());
                if (test) {
                    post(ExitEvent.NAME, ExitEvent.valueOf(playerIds, id));
                    return;
                }
                logger.debug("房间[{}]结束游戏！", id);
                SettleTo settleTo = getSettleTo(playerIds, reason);
                //大厅结算数据(异步)
                post(GameServerEvent.NAME, GameServerEvent.valueOf(settleTo, SocketEnum.REQUEST_SETTLE, new Callback<SettleVo>() {
                    @Override
                    public void call(SettleVo body) {
                        syncPost(PushEvent.NAME, PushEvent.valueOf(body, playerIds, SocketEnum.PUSH_SETTLE));
                        post(ExitEvent.NAME, ExitEvent.valueOf(playerIds, id));
                    }

                    @Override
                    public void onException(Exception ex, SettleVo body) {
                        //结算失败
                        post(ExitEvent.NAME, ExitEvent.valueOf(playerIds, id));
                        logger.error("结算超时，发生错误！", ex);
                    }
                }));
            }
        });
    }

    public SettleTo getSettleTo(Collection<Long> playerIds, ExitReason reason) {
        //正常死亡
        if (reason == ExitReason.DEAD) {
            Unit win = getWin();
            Unit lose = getLose(win);
            return SettleTo.valueOf(win.getId(), win.getValue(UnitValue.HP_MAX), win.getValue(UnitValue.HP), 0, lose.getId(), 0, reason);
        } else {//非正常结束
            List<Unit> all = new ArrayList<>(units.values());
            Unit win = all.get(0);
            Unit lose = all.get(1);
            return SettleTo.valueOf(win.getId(), win.getValue(UnitValue.HP_MAX), 0, 0, lose.getId(), 0, reason);
        }
    }

    private Unit getWin() {
        for (Unit unit : units.values()) {
            if (!unit.hasState(UnitState.DEAD)) {
                return unit;
            }
        }
        throw new RuntimeException("获取获胜方发生错误！");
    }

    private Unit getLose(Unit win) {
        for (Unit unit : units.values()) {
            if (win.getId() != unit.getId()) {
                return unit;
            }
        }
        throw new RuntimeException("获取失败方发生错误！");
    }

    public RoomConfig getConfig() {
        return configService.getRoomConfig(type);
    }

    public void execute(long playerId, int skillId) {
        endTurn();
        if (this.getType() == RoomType.PVP) {
            roundTimer(new Date());
        }
    }

    public void post(String name, Object body) {
        ((GameService) timer).post(name, body);
    }

    public void syncPost(String name, PushEvent pushEvent) {
        Collection<Long> pushed = new HashSet<>();
        for (Long id : pushEvent.getPlayerIds()) {
            //排除离线玩家
            if (offline(id) || id < 0) {
                continue;
            }
            pushed.add(id);
        }
        pushEvent.setPlayerIds(pushed);
        ((GameService) timer).syncPost(name, pushEvent);
    }

    public boolean offline(long playerId) {
        Unit unit = units.get(playerId);
        return unit.hasState(UnitState.OFFLINE);
    }

    public void destory() {
        state = END;
        Collection<Long> ids = new HashSet<>(units.keySet());
        for (Long id : ids) {
            Unit unit = units.remove(id);
            if (unit != null) {
                unit.destory();
            }
            unit = null;
        }
        chessBoard = null;
        trushees = null;
        current = null;

    }

    public void setType(RoomType type) {
        this.type = type;
    }

    public void addWork(RoomRunner runner, Date time) {
        timer.addTimer(this, runner, time);
    }

    public void addWork(RoomWork work) {
        timer.addTimer(work);
    }

    public void submit(RoomRunner runner) {
        timer.submit(this, runner);
    }

    public ConcurrentMap<Long, Unit> getUnits() {
        return units;
    }

    public void setUnits(ConcurrentMap<Long, Unit> units) {
        this.units = units;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public RoomTimer getTimer() {
        return timer;
    }

    public void setTimer(RoomTimer timer) {
        this.timer = timer;
    }

    public Unit getCurrent() {
        return current;
    }

    public void setCurrent(Unit current) {
        this.current = current;
    }

    public ChessBoard getChessBoard() {
        return chessBoard;
    }

    public void setChessBoard(ChessBoard chessBoard) {
        this.chessBoard = chessBoard;
    }

    public boolean isOver() {
        return state == END;
    }

    public boolean isBegin() {
        return state == BEGIN;
    }

    public ConfigService getConfigService() {
        return configService;
    }

    public boolean isTest() {
        return test;
    }

    public void setTest(boolean test) {
        this.test = test;
    }

    public abstract RoomVo toVo();

    public int getCountRound() {
        return countRound;
    }

    public List<Grid> test() {
        if (chessBoard != null) {
            List<Grid> list = new ArrayList<>();
            Grid[] s = chessBoard.getGrids();
            for (Grid g : s) {
                list.add(g);
            }
            return list;
        } else {
            return null;
        }
    }
}
